/**
 * Copyright 2018-2020 stylefeng & fengshuonan (https://gitee.com/stylefeng)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.stylefeng.guns.core.aop;

import cn.stylefeng.guns.core.common.annotion.BussinessLog;
import cn.stylefeng.guns.core.common.constant.dictmap.base.AbstractDictMap;
import cn.stylefeng.guns.core.common.constant.dictmap.factory.DgMethodDoFactory;
import cn.stylefeng.guns.core.log.LogManager;
import cn.stylefeng.guns.core.log.LogObjectHolder;
import cn.stylefeng.guns.core.log.factory.LogTaskFactory;
import cn.stylefeng.guns.core.shiro.ShiroKit;
import cn.stylefeng.guns.core.shiro.ShiroUser;
import cn.stylefeng.guns.core.util.Contrast;
import cn.stylefeng.guns.core.util.RequestToJsonToObjectUtils;
import cn.stylefeng.roses.core.util.HttpContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

/**
 * 日志记录
 *
 * @author fengshuonan
 * @date 2016年12月6日 下午8:48:30
 */
@Aspect
@Component
public class LogAop {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Pointcut(value = "@annotation(cn.stylefeng.guns.core.common.annotion.BussinessLog)")
    public void cutService() {
    }

    @Around("cutService()")
    public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {

        //先执行业务
        Object result = point.proceed();

        try {
            handle(point);
        } catch (Exception e) {
            log.error("日志记录出错!", e);
        }

        return result;
    }

    private void handle(ProceedingJoinPoint point) throws Exception {

        //获取拦截的方法名
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        String methodName = currentMethod.getName();

        //如果当前用户未登录，不做日志
        ShiroUser user = ShiroKit.getUser();
        if (null == user) {
            return;
        }

        //获取拦截方法的参数
        String className = point.getTarget().getClass().getName();
        Object[] params = point.getArgs();

        //获取操作名称
        BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class);
        String bussinessName = annotation.value();
        String key = annotation.key();
        Class dictClass = annotation.dict();

        StringBuilder sb = new StringBuilder();
        for (Object param : params) {
            sb.append(param);
            sb.append(" & ");
        }

        //如果涉及到修改,比对变化
        String msg;
        StringBuffer strBuf = new StringBuffer();
        //*****************************dxl****************************
		//如果包含[]的话，认为是传进来的数组（只接受integer或string的数组）
        if(key.contains("[") && key.contains("]")){
        	String key0 = key.replaceAll(" ", "");
            	int index1=key0.indexOf("[");
            	int index2;
            	String keyArrayName;
            	if(key0.contains(":")){
            		index2 = key0.indexOf(":");
            		key = key0.substring(index2+1);
            		keyArrayName = key0.substring(index1+2,index2);
            	}else if(key0.contains("：")){
            		index2 = key0.indexOf("：");
            		key = key0.substring(index2+1);
            		keyArrayName = key0.substring(index1+2,index2);
    			}else {
    				key = key0.substring(index1+2);
    				keyArrayName = key0.substring(index1+2);
    			}
            	String type = key0.substring(0, index1);
            	//key例子：Integer[] userIds:account,name,birthday,sex,email,phone,roleid,deptid,createtime,jobnum,position,grade
            	//上面是处理key，type处理为Integer；keyArrayName处理为userIds；key处理为account,name,birthday,...
				//注意：可以有空格，冒号：(不分中英文，必须有)；尖括号[]必须有；后面逗号必须问英文

			    //拿到request
            	HttpServletRequest request = HttpContext.getRequest();

				//拿到相应的字典
            	AbstractDictMap dictMap = (AbstractDictMap) dictClass.newInstance();

            	//拿到keyArrayName对应的DgMethod方法，如果没有则为null（在AbstractDictMap中自己加的）
            	String dgMethodName = dictMap.getDgDictoryName(keyArrayName);

            	//判断如果数组类型为integer，要做相应数据处理
            	if("Integer".equals(type)){
            		//从request拿到数组
            		String[] integerKeys = request.getParameterValues(keyArrayName);
            		//遍历数组，一个元素如果在相应字典中有DgMethod方法包装就先映射调用方法再包装，没有方法DgMethod则用字典包装
            		for(int j = 0; integerKeys != null && j < integerKeys.length; j++){
            			//一个元素如果在相应字典中有DgMethod方法包装就先映射调用方法再包装
            			 if (dgMethodName != null) {
            			 	//使用DgMethodDoFactory反射调用dgConstantFactory中对应dgMethodName的方法，查出来应为一个对象，利用工具包装成Map
            				 Map<String, String> parameters = DgMethodDoFactory.createMapWarpper(Integer.valueOf(integerKeys[j]), dgMethodName);
            				 //使用Contrast.dgParseMutiKey方法将属性、属性名称、属性值包装成字符串（dgParseMutiKey自己加的）
            				 strBuf.append(Contrast.dgParseMutiKey(dictMap, key, parameters)).append(";;;;;;");//“;;;”为一个换行符

            				 //如果在相应字典中没有DgMethod方法，则用字典包装
                         } else {
                        	 Map<String, String> parameter = new HashMap<>();
                        	 //包装此字段
                        	 parameter.put(keyArrayName, integerKeys[j]);
                        	 strBuf.append(Contrast.dgParseMutiKey(dictMap, keyArrayName, parameter));
                         }
            		}
					//判断如果数组类型为integer，要做相应数据处理
            	}else if ("String".equals(type)) {
					//从request拿到数组
            		String[] stringKeys = request.getParameterValues(keyArrayName);
					//遍历数组，一个元素如果在相应字典中有DgMethod方法包装就先映射调用方法再包装，没有方法DgMethod则用字典包装
            		for(int j = 0; stringKeys != null && j < stringKeys.length; j++){
            			 if (dgMethodName != null) {
							 //使用DgMethodDoFactory反射调用dgConstantFactory中对应dgMethodName的方法，查出来应为一个对象，利用工具包装成Map
            				 Map<String, String> parameters = DgMethodDoFactory.createMapWarpper(stringKeys[j], dgMethodName);
							 //使用Contrast.dgParseMutiKey方法将属性、属性名称、属性值包装成字符串（dgParseMutiKey自己加的）
            				 strBuf.append(Contrast.dgParseMutiKey(dictMap, key, parameters)).append(";;;;;;");//“;;;”为一个换行符

							 //如果在相应字典中没有DgMethod方法，则用字典包装
                         } else {
                        	 Map<String, String> parameter = new HashMap<>();
							 //包装此字段
                        	 parameter.put(keyArrayName, stringKeys[j]);
                        	 strBuf.append(Contrast.dgParseMutiKey(dictMap, keyArrayName, parameter));
                         }
            		}
    			}
    			//strBuf转成字符串，当作日志消息
            	msg = strBuf.toString();	
        	
        	//************************************************************************************
			//如果包含<>的话，认为是传进来的List。（还没试过，不一定好使）
        }else if(key.contains("<") &&  key.contains(">")){
        	String key0 = key.replaceAll(" ", "");
        	int index1=key.indexOf("<");
        	int index2=key.indexOf(">");
        	int index3;
        	String objectName = null;
        	String keyName;
        	if(key0.contains(":")){
        		index3 = key0.indexOf(":");
        		key = key0.substring(index3+1);
        		keyName = key0.substring(index2+1,index3);
        		objectName = key0.substring(index1+1,index2);
        	}else if(key0.contains("：")){
        		index3 = key0.indexOf("：");
        		key = key0.substring(index3+1);
        		keyName = key0.substring(index2+1,index3);
        		objectName = key0.substring(index1+1,index2);
			}else {
				key = key0.substring(index2+1);
				keyName = key0.substring(index2+1);
			}
			//key例子：List<User> users:account,name,birthday,sex,email,phone,roleid,deptid,createtime,jobnum,position,grade
			//上面是处理key，objectName为对象名，如User；keyName为List的变量名，如users；key为要记录日志的字段名，如account,name,birthday,...
			//注意：可以用空格，冒号：(不分中英文，必须有)；尖括号<>必须有；后面逗号必须问英文

			//拿到request
			HttpServletRequest request = HttpContext.getRequest();

			//拿到名字为objectName（如User）的对象反射
			//大致、大概、应该、可能、也许、或许、好像、好似、神似、貌似 好使吧
        	Object obj=Class.forName(objectName).newInstance();

			//利用工具类RequestToJsonToObjectUtils的requestToObjectList方法，把request中的把对象转成List
			// （现在还没试过，不一定好使）
        	List<Object> objects = RequestToJsonToObjectUtils.requestToObjectList(request, obj.getClass());

			//拿到dictMap
        	AbstractDictMap dictMap = (AbstractDictMap) dictClass.newInstance();

			////拿到keyName对应的DgMethod方法，如果没有则为null（在AbstractDictMap中自己加的）
        	String dgMethodName = dictMap.getDgDictoryName(keyName);

        	//一个元素如果在相应字典中有DgMethod方法包装就先映射调用方法再包装（参数为对象。在此处一般应一定有DgMethod方法）
        	if(dgMethodName != null){
        		for(int k = 0; k < objects.size(); k++ ){
				//使用DgMethodDoFactory反射调用dgConstantFactory中对应dgMethodName的方法，查出来应为一个对象，利用工具包装成Map（dgConstantFactory自己加的）
           		 Map<String, String> parameters = DgMethodDoFactory.createMapWarpper(objects.get(k),dgMethodName);
				//使用Contrast.dgParseMutiKey方法将属性、属性名称、属性值包装成字符串（dgParseMutiKey自己加的）
           		 strBuf.append(Contrast.dgParseMutiKey(dictMap, key, parameters)).append(";;;;;;");
        		}

			//没有DgMethod方法时就直接把类名（model)放进去.(用于开发)
        	}else {
        		strBuf.append("objectName");
			}
			//strBuf转成字符串，当作日志消息
        	msg = strBuf.toString();

        	//guns写的，增加了几个汉字判断
			//重要改变：key的形式,，如account{account,name,birthday,sex,email,phone,jobnum,position,grade,entrytime,dimissiontime}
			//{ }前的字段要为索引即确定唯一性，如果一个不行就多个如：account,name,birthday
			//{ }中的字段即为改变时要记录的字段，英文逗号隔开
        }else if (bussinessName.contains("修改") || bussinessName.contains("编辑")
        		  || bussinessName.contains("更新") || bussinessName.contains("重置")
        		  || bussinessName.contains("更改")) {
			Object obj1 = LogObjectHolder.me().get();
            Map<String, String> obj2 = HttpContext.getRequestParameters();
            //用Contrast.contrastObj对比修改前后的对象，把改变的转成日志消息即string
            msg = Contrast.contrastObj(dictClass, key, obj1, obj2);

			//guns写的，key的形式，如account。字段要为索引即确定唯一性，如果一个不行就多个如：account,name,birthday。
        } else {
            Map<String, String> parameters = HttpContext.getRequestParameters();
            AbstractDictMap dictMap = (AbstractDictMap) dictClass.newInstance();
			//用Contrast.contrastObj对比修改前后的对象，把改变的转成日志消息即string
            msg = Contrast.parseMutiKey(dictMap, key, parameters);
        }

		//guns写的，把日志名称、操作者、方法名、日志消息等封装，存进数据库中
        LogManager.me().executeLog(LogTaskFactory.bussinessLog(user.getId(), bussinessName, className, methodName, msg));
    }
}